﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Threading;
using SimpleServiceBus.Endpoint;
using SimpleServiceBus.Messages;
using SimpleServiceBus.Messages.Management.Monitoring;
using SimpleServiceBus.Server.Configuration;
using SimpleServiceBus.Utilities;

namespace SimpleServiceBus.Server.Management.EndpointMonitor
{
    public class EndpointMonitorServer : TimerServiceBase, IEndpointMonitorServerService
    {
        private readonly ReaderWriterLock _accessLock = new ReaderWriterLock();
        private readonly Dictionary<string, ActiveEndpoint> _activeEndpoints = new Dictionary<string, ActiveEndpoint>();

        private TimeSpan _readTimeout = TimeSpan.FromSeconds(30);
        private TimeSpan _writeTimeout = TimeSpan.FromSeconds(30);

        public EndpointMonitorServer() : base(TimeSpan.FromMinutes(5))
        {
        }

        public TimeSpan ReadTimeout
        {
            get { return _readTimeout; }
            set { _readTimeout = value; }
        }

        public TimeSpan WriteTimeout
        {
            get { return _writeTimeout; }
            set { _writeTimeout = value; }
        }

        #region IEndpointMonitorServerService Members

        public event EventHandler<EndpointStatusEventArgs> EndpointStatusExpired;
        public event EventHandler<EndpointStatusEventArgs> EndpointStatusUpdated;

        public IMessagingEndpoint Endpoint { get; set; }

        public TimeSpan EndpointMonitorInterval
        {
            get { return Interval; }
            set { Interval = value; }
        }

        public virtual void UpdateEndpointStatus(EndpointMessage message)
        {
            //malformed endpoint, no good to us.
            if (message.EndpointID == null)
                return;

            ActiveEndpoint endpoint = GetActiveEndpoint(message.EndpointID) ?? new ActiveEndpoint();

            endpoint.EndpointID = message.EndpointID;
            endpoint.LastStatusReceived = DateTime.Now.ToUniversalTime();
            if (message is EndpointStatusMessage)
                endpoint.LastStatus = (EndpointStatusMessage) message;
            else if (message is EndpointErrorNotification)
                endpoint.LastError = (EndpointErrorNotification) message;

            if (message is EndpointOfflineNotification)
            {
                //We should publish this message ASAP - someone may need
                //to know sooner rather than later.
                Endpoint.MessageBus.Publish(message);
                RemoveActiveEndpoint(message.EndpointID);
            }
            else
            {
                //We'll publish these, too
                if (message is EndpointOnlineNotification ||
                    message is EndpointErrorNotification)
                    Endpoint.MessageBus.Publish(message);

                UpdateActiveEndpoint(endpoint);
            }

            OnEndpointStatusUpdated(endpoint);
        }

        #endregion

        protected override void PerformTimerAction()
        {
            List<ActiveEndpoint> endpoints = GetAllActiveEndpoints();
            endpoints.ForEach
                (
                endpoint => { if (endpoint.IsStatusExpired) OnEndpointStatusExpired(endpoint); }
                );
        }

        public List<ActiveEndpoint> GetAllActiveEndpoints()
        {
            using (new ReaderLock(_accessLock, _readTimeout))
            {
                return new List<ActiveEndpoint>(_activeEndpoints.Values);
            }
        }

        public ActiveEndpoint GetActiveEndpoint(string endpointId)
        {
            using (new ReaderLock(_accessLock, _readTimeout))
            {
                if (_activeEndpoints.ContainsKey(endpointId))
                    return _activeEndpoints[endpointId];
                return null;
            }
        }

        public bool IsEndpointActive(string endpointId)
        {
            using (new ReaderLock(_accessLock, _readTimeout))
            {
                return _activeEndpoints.ContainsKey(endpointId);
            }
        }

        protected void UpdateActiveEndpoint(ActiveEndpoint endpoint)
        {
            using (new WriterLock(_accessLock, _writeTimeout))
            {
                if (_activeEndpoints.ContainsKey(endpoint.EndpointID))
                    _activeEndpoints[endpoint.EndpointID] = endpoint;
                else
                    _activeEndpoints.Add(endpoint.EndpointID, endpoint);
            }
        }

        protected void RemoveActiveEndpoint(string endpointId)
        {
            //Remove endpoint from list
            using (new WriterLock(_accessLock, _writeTimeout))
            {
                if (_activeEndpoints.ContainsKey(endpointId))
                {
                    _activeEndpoints.Remove(endpointId);
                }
            }
        }

        protected virtual void OnEndpointStatusUpdated(ActiveEndpoint updatedEndpoint)
        {
            EventHandler<EndpointStatusEventArgs> evt = EndpointStatusUpdated;

            if (evt != null)
                evt(this, new EndpointStatusEventArgs(updatedEndpoint));
        }

        protected virtual void OnEndpointStatusExpired(ActiveEndpoint expiredEndpoint)
        {
            //Publish event to bus
            EndpointStatusExpiredNotification expirationNotification =
                CreateEndpointStatusExpiredNotification(expiredEndpoint);

            if (Endpoint.MessageBus != null)
                Endpoint.MessageBus.Publish(expirationNotification);

            //Publish event to runtime
            EventHandler<EndpointStatusEventArgs> evt = EndpointStatusExpired;

            if (evt != null)
                evt(this, new EndpointStatusEventArgs(expiredEndpoint));


            RemoveActiveEndpoint(expiredEndpoint.EndpointID);
        }

        protected virtual EndpointStatusExpiredNotification CreateEndpointStatusExpiredNotification(
            ActiveEndpoint expiredEndpoint)
        {
            return new EndpointStatusExpiredNotification
                       {
                           EndpointID = expiredEndpoint.EndpointID,
                           LastStatusReceived = expiredEndpoint.LastStatusReceived,
                           LastStatusMessage = expiredEndpoint.LastStatus
                       };
        }

        #region Nested type: ActiveEndpoint

        public class ActiveEndpoint
        {
            public string EndpointID { get; set; }
            public DateTime LastStatusReceived { get; set; }
            public EndpointStatusMessage LastStatus { get; set; }
            public EndpointErrorNotification LastError { get; set; }

            public bool IsStatusExpired
            {
                get
                {
                    return LastStatus.StatusExpirationUtcTime != DateTime.MinValue &&
                           LastStatus.StatusExpirationUtcTime < DateTime.Now.ToUniversalTime();
                }
            }
        }

        #endregion
    }
}